EFCore.FSharp also includes a number of helper methods for allowing a more F#-like experience while interacting with a DbContext.
They can be found in EntityFrameworkCore.FSharp.DbContextHelpers
Given a context with a Blog defined as such
[<CLIMutable>]
type Blog = {
[<Key>]
Id : Guid
Title : string
Content : string
}
type MyContext () =
inherit DbContext()
[<DefaultValue>]
val mutable private _blogs : DbSet<Blog>
member this.Blogs with get() = this._blogs and set v = this._blogs <- v
We can use the helper methods like so
let interactWithContext (ctx:MyContext) =
let originalBlogPost = {
Id = (Guid.NewGuid())
Title = "My Title"
Content = "My original content"
}
originalBlogPost |> addEntity ctx |> ignore
saveChanges ctx |> ignore
let blogPostReadFromDb =
match tryFindEntity<Blog> ctx originalBlogPost.Id with
| Some b -> b
| None -> failwithf "Could not entity of type %A with identifier %A" typeof<Blog> originalBlogPost.Id
let modifiedBlogPost = { blogPostReadFromDb with Content = "My updated content" }
// This method is needed as we are "updating" the database with what is technically a new object
// We specify the key, allowing us to accommodate composite keys
updateEntity ctx (fun b -> b.Id :> obj) modifiedBlogPost |> ignore
saveChanges ctx |> ignore
let updatedBlogFromDb =
match tryFindEntity<Blog> ctx originalBlogPost.Id with
| Some b -> b
| None -> failwithf "Could not find entity of type %A with identifier %A" typeof<Blog> originalBlogPost.Id
updatedBlogFromDb.Content = "My updated content" // This will be true
An advantage of the generalised updateEntity function is that it allows us to use partial application to easily create methods for specific entities. For example:
let ctx = new MyContext()
// Partially applied function, all that it needs now is the Blog entity
let updateBlog = updateEntity ctx (fun (b:Blog) -> b.Id :> obj)
let myBlog = ctx.Blogs |> Seq.head
// No need to specify context or key, that's already been done
updateBlog { myBlog with Content = "Updated content" }
We also have methods and functions for DbSet/IQueryable to replace usage of FirstOrDefault and FirstOrDefaultAsync:
let queryingContext (ctx:MyContext) =
let allPosts = toListAsync ctx.Blogs |> Async.RunSynchronously
let firstBlog =
match tryFirst ctx.Blogs with
| Some b -> b
| None -> failwithf "no blogs"
let filteredBlog =
let maybeBlog = tryFilterFirst <@ fun x -> x.Title = "some title" @> ctx.Blogs
match maybeBlog with
| Some b -> b
| None -> failwithf "no blogs founded with the title"
let firstBlogLinq =
match ctx.Blogs.TryFirst() with
| Some b -> b
| None -> failwithf "no blogs"
let filteredBlogLinq =
match ctx.Blogs.TryFirst(fun x -> x.Title = "some title") with
| Some b -> b
| None -> failwithf "no blogs founded with the title"
let advancedQuery =
query {
for b in ctx.Blogs do
where (b.Title = "My title" && b.Content = "Some content")
select b.Id
}
|> tryFirstAsync
|> Async.RunSynchronously
()
All methods in EntityFrameworkCore.FSharp.DbContextHelpers also have Async variants with support for async { ... } expressions
Multiple items
type CLIMutableAttribute =
inherit Attribute
new: unit -> CLIMutableAttribute
--------------------
new: unit -> CLIMutableAttribute
type Blog =
{
Id: Guid
Title: string
Content: string
}
Multiple items
type KeyAttribute =
inherit Attribute
new: unit -> unit
<summary>Denotes one or more properties that uniquely identify an entity.</summary>
--------------------
KeyAttribute() : KeyAttribute
Blog.Id: Guid
Multiple items
[<Struct>]
type Guid =
new: b: byte array -> unit + 6 overloads
member CompareTo: value: Guid -> int + 1 overload
member Equals: g: Guid -> bool + 1 overload
member GetHashCode: unit -> int
member ToByteArray: unit -> byte array + 1 overload
member ToString: unit -> string + 2 overloads
member TryFormat: utf8Destination: Span<byte> * bytesWritten: byref<int> * ?format: ReadOnlySpan<char> -> bool + 1 overload
member TryWriteBytes: destination: Span<byte> -> bool + 1 overload
static member (<) : left: Guid * right: Guid -> bool
static member (<=) : left: Guid * right: Guid -> bool
...
<summary>Represents a globally unique identifier (GUID).</summary>
--------------------
Guid ()
Guid(b: byte array) : Guid
Guid(b: ReadOnlySpan<byte>) : Guid
Guid(g: string) : Guid
Guid(b: ReadOnlySpan<byte>, bigEndian: bool) : Guid
Guid(a: int, b: int16, c: int16, d: byte array) : Guid
Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Blog.Title: string
Multiple items
val string: value: 'T -> string
--------------------
type string = String
Blog.Content: string
Multiple items
type MyContext =
new: unit -> MyContext
member Blogs: IQueryable<Blog>
--------------------
new: unit -> MyContext
Multiple items
type DefaultValueAttribute =
inherit Attribute
new: unit -> DefaultValueAttribute + 1 overload
member Check: bool
--------------------
new: unit -> DefaultValueAttribute
new: check: bool -> DefaultValueAttribute
MyContext._blogs: obj
val set: elements: seq<'T> -> Set<'T> (requires comparison)
val interactWithContext: ctx: MyContext -> bool
val ctx: MyContext
val originalBlogPost: Blog
Guid.NewGuid() : Guid
val ignore: value: 'T -> unit
val blogPostReadFromDb: Blog
union case Option.Some: Value: 'T -> Option<'T>
val b: Blog
union case Option.None: Option<'T>
val failwithf: format: Printf.StringFormat<'T,'Result> -> 'T
val typeof<'T> : Type
val modifiedBlogPost: Blog
type obj = Object
val updatedBlogFromDb: Blog
val updateBlog: (Blog -> obj)
val myBlog: Blog
property MyContext.Blogs: Linq.IQueryable<Blog> with get, set
module Seq
from Microsoft.FSharp.Collections
val head: source: seq<'T> -> 'T
val queryingContext: ctx: MyContext -> unit
val allPosts: obj
Multiple items
type Async =
static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool>
static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload
static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool>
static member CancelDefaultToken: unit -> unit
static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>>
static member Choice: computations: seq<Async<'T option>> -> Async<'T option>
static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads
static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
...
--------------------
type Async<'T>
static member Async.RunSynchronously: computation: Async<'T> * ?timeout: int * ?cancellationToken: Threading.CancellationToken -> 'T
val firstBlog: obj
val b: obj
val filteredBlog: obj
val maybeBlog: obj option
val firstBlogLinq: obj
val filteredBlogLinq: obj
val advancedQuery: obj
val query: Linq.QueryBuilder
custom operation: where (bool)
Calls Linq.QueryBuilder.Where
custom operation: select ('Result)
Calls Linq.QueryBuilder.Select